package interpreter;

import grammar.ApplicationTree;
import grammar.FunBaseVisitor;
import grammar.FunLexer;
import grammar.FunParser;

import java.util.ArrayList;
import java.util.Stack;

public class TestFunVisitor extends FunBaseVisitor<Integer> {
    private FunctionStorage storage = new FunctionStorage(this);
    private Stack<StackPage> stack = new Stack<StackPage>(); // argument stack

    @Override
    public Integer visitProgram(FunParser.ProgramContext ctx) {
        return visit(ctx.expression(0));
    }

    @Override
    public Integer visitExpressionInBrackets(FunParser.ExpressionInBracketsContext ctx) {
        return visit(ctx.expression());
    }

    @Override
    public Integer visitLet(FunParser.LetContext ctx) {
        storage.addFunction(ctx);
        return visit(ctx.getChild(5));
    }


    @Override
    public Integer visitApplications(FunParser.ApplicationsContext ctx) {
        FunParser.ApplicationsListContext app = ctx.applicationsList();
        visit(app);
        if (app.application.unary) {
            return visit(app.application.func);
        }
        ArrayList<Integer> arguments = new ArrayList<Integer>();
        ApplicationTree cur = app.application;
        while (!cur.unary) {
            arguments.add(visit(app.application.func));
            cur = cur.next;
        }
        return storage.callFunction(cur.func.getText(), arguments); //To change body of overridden methods use File | Settings | File Templates.
    }

  /*  @Override
    public Integer visitApplication(FunParser.ApplicationContext ctx) {

        if (ctx.application.unary) {
            return visit(ctx.application.func);
        }
        //ToDO support functions
       return 0;
       */
    //ctx.
     /*   if (ctx.expr5().isEmpty())
            return 0;
        if (ctx.expr5().size()==1) {
            return visit(ctx.expr5().get(0));

        }

        String functionName = ctx.expr5(0).getChild(0).getText();

        // works only for functions with one argument
        // ToDO support multiargument functions

        // get function arguments
        ArrayList<Integer> arguments = new ArrayList<Integer>();
        arguments.add(visit(ctx.expr5().get(1).getChild(1)));
        return storage.callFunction(functionName, arguments);   */
    // }


    @Override
    public Integer visitIfThenElse(FunParser.IfThenElseContext ctx) {
        Integer condition = visit(ctx.expression(0));
        if (intToBool(condition)) {
            return visit(ctx.expression(1));
        } else {
            return visit(ctx.expression(2));
        }
    }

    @Override
    public Integer visitLogic(FunParser.LogicContext ctx) {
        int operator = ctx.operator.getType();

        boolean left = intToBool(visit(ctx.getChild(0)));

        if ((left && operator == FunLexer.OR) || (!left && operator == FunLexer.AND)) {
            return boolToInt(left);
        } else {
            boolean right = intToBool(visit(ctx.getChild(2)));
            switch (operator) {
                case FunLexer.AND:
                    return boolToInt(right);
                case FunLexer.OR:
                    return boolToInt(right);
                default:
                    return 0;
            }
        }
    }

    @Override
    public Integer visitCompare(FunParser.CompareContext ctx) {
        int left = visit(ctx.getChild(0));
        int right = visit(ctx.getChild(2));
        int operator = ctx.operator.getType();
        switch (operator) {
            case FunLexer.EQUAL:
                return boolToInt(left == right);

            case FunLexer.NOT_EQUAL:
                return boolToInt(left != right);

            case FunLexer.LESS:
                return boolToInt(left < right);

            case FunLexer.MORE:
                return boolToInt(left > right);

            case FunLexer.LESS_OR_EQUAL:
                return boolToInt(left <= right);

            case FunLexer.MORE_OR_EQUAL:
                return boolToInt(left >= right);

            default:
                return 0;
        }
    }

    @Override
    public Integer visitAddSub(FunParser.AddSubContext ctx) {
        int left = visit(ctx.getChild(0));
        int right = visit(ctx.getChild(2));
        int operator = ctx.operator.getType();
        switch (operator) {
            case FunLexer.PLUS:
                return left + right;

            case FunLexer.MINUS:
                return left - right;

            default:
                return 0;
        }
    }

    @Override
    public Integer visitMulDiv(FunParser.MulDivContext ctx) {
        int left = visit(ctx.getChild(0));
        int right = visit(ctx.getChild(2));
        if (ctx.operator.getType() == FunLexer.MULT) {
            return left * right;
        } else if (ctx.operator.getType() == FunLexer.MOD) {
            return left % right;
        } else if (ctx.operator.getType() == FunLexer.DIV) {
            return left / right;
        } else {
            return 0;
        }
    }

    @Override
    public Integer visitIdentifier(FunParser.IdentifierContext ctx) {
        if (stack.isEmpty()) {
            int res = storage.callFunction(ctx.getText(), null);
            return res;
        }
        StackPage page = stack.peek();
        if (page.arguments.containsKey(page.function + "." + ctx.getText())) {
            return page.arguments.get(page.function + "." + ctx.getText());
        } else {
            System.err.println("Unknown identifier: " + ctx.getText());
            return 0;
        }
    }

    @Override
    public Integer visitLiteral(FunParser.LiteralContext ctx) {
        return Integer.valueOf(ctx.LITERAL().getText());
    }

    private boolean intToBool(int x) {
        return x != 0;
    }

    private int boolToInt(boolean x) {
        if (x) return 1;
        else return 0;
    }

    public void addStackPage(StackPage page) {
        this.stack.push(page);
    }

    public void removeStackPage() {
        this.stack.pop();
    }
}
